package cn.anxin.a9.avplayer;

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.util.Log;

import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class AVGLRender
        implements GLSurfaceView.Renderer {

    private final static String TAG = "rnd2dplayer";

    int mHeight = 0;
    ByteBuffer mUByteBuffer = null;
    ByteBuffer mVByteBuffer = null;
    int mWidth = 0;
    ByteBuffer mYByteBuffer = null;
    FloatBuffer positionBuffer = null;
    float[] positionBufferData;
    int positionSlot = 0;
    int programHandle = 0;
    int texRangeSlot = 0;
    int[] texture = new int[3];
    int[] textureSlot = new int[3];
    int vertexShader = 0;
    int yuvFragmentShader = 0;
    byte[] yuvData = null;
    float[] textCoodBufferData;
    FloatBuffer textCoodBuffer = null;
    boolean bNeedSleep = true;
    private int width;
    private int height;

    private int x,y;
    private int videoWidth;
    private int videoHeight;

    public AVGLRender(GLSurfaceView paramGLSurfaceView) {
        float[] arrayOfFloat1 = new float[16];


        arrayOfFloat1[0] = 0.0F;
        arrayOfFloat1[1] = 0.0F;
        arrayOfFloat1[2] = 0.0F;
        arrayOfFloat1[3] = 1.0F;

        arrayOfFloat1[4] = 0.0F;
        arrayOfFloat1[5] = 1.0F;
        arrayOfFloat1[6] = 0.0F;
        arrayOfFloat1[7] = 1.0F;

        arrayOfFloat1[8] = 1.0F;
        arrayOfFloat1[9] = 0.0F;
        arrayOfFloat1[10] = 0.0F;
        arrayOfFloat1[11] = 1.0F;

        arrayOfFloat1[12] = 1.0F;
        arrayOfFloat1[13] = 1.0F;
        arrayOfFloat1[14] = 0.0F;
        arrayOfFloat1[15] = 1.0F;

        this.textCoodBufferData = arrayOfFloat1;


        float[] arrayOfFloat = new float[16];


        arrayOfFloat[0] = -1.0F;
        arrayOfFloat[1] = 1.0F;
        arrayOfFloat[2] = 0.0F;
        arrayOfFloat[3] = 1.0F;

        arrayOfFloat[4] = -1.0F;
        arrayOfFloat[5] = -1.0F;
        arrayOfFloat[6] = 0.0F;
        arrayOfFloat[7] = 1.0F;

        arrayOfFloat[8] = 1.0F;
        arrayOfFloat[9] = 1.0F;
        arrayOfFloat[10] = 0.0F;
        arrayOfFloat[11] = 1.0F;

        arrayOfFloat[12] = 1.0F;
        arrayOfFloat[13] = -1.0F;
        arrayOfFloat[14] = 0.0F;
        arrayOfFloat[15] = 1.0F;


        this.positionBufferData = arrayOfFloat;


        paramGLSurfaceView.setEGLContextClientVersion(2);
    }

    public static int compileShader(String paramString, int paramInt) {
        int i = GLES20.glCreateShader(paramInt);
        if (i != 0) {
            int[] arrayOfInt = new int[1];
            GLES20.glShaderSource(i, paramString);
            GLES20.glCompileShader(i);
            GLES20.glGetShaderiv(i, GLES20.GL_COMPILE_STATUS, arrayOfInt, 0);
            if (arrayOfInt[0] == 0) {
                GLES20.glDeleteShader(i);
                i = 0;
            }
        }
        return i;
    }

    public long createShaders() {
        String fragmentShaderCode = "uniform sampler2D Ytex;\n";
        fragmentShaderCode = fragmentShaderCode + "uniform sampler2D Utex;\n";
        fragmentShaderCode = fragmentShaderCode + "uniform sampler2D Vtex;\n";
        fragmentShaderCode = fragmentShaderCode + "precision mediump float;  \n";
        fragmentShaderCode = fragmentShaderCode + "varying vec4 VaryingTexCoord0; \n";
        fragmentShaderCode = fragmentShaderCode + "vec4 color;\n";
        fragmentShaderCode = fragmentShaderCode + "void main()\n";
        fragmentShaderCode = fragmentShaderCode + "{\n";
        fragmentShaderCode = fragmentShaderCode + "float yuv0 = (texture2D(Ytex,VaryingTexCoord0.xy)).r;\n";
        fragmentShaderCode = fragmentShaderCode + "float yuv1 = (texture2D(Utex,VaryingTexCoord0.xy)).r;\n";
        fragmentShaderCode = fragmentShaderCode + "float yuv2 = (texture2D(Vtex,VaryingTexCoord0.xy)).r;\n";
        fragmentShaderCode = fragmentShaderCode + "\n";
        fragmentShaderCode = fragmentShaderCode + "color.r = yuv0 + 1.4022 * yuv2 - 0.7011;\n";
        fragmentShaderCode = fragmentShaderCode + "color.r = (color.r < 0.0) ? 0.0 : ((color.r > 1.0) ? 1.0 : color.r);\n";
        fragmentShaderCode = fragmentShaderCode + "color.g = yuv0 - 0.3456 * yuv1 - 0.7145 * yuv2 + 0.53005;\n";
        fragmentShaderCode = fragmentShaderCode + "color.g = (color.g < 0.0) ? 0.0 : ((color.g > 1.0) ? 1.0 : color.g);\n";
        fragmentShaderCode = fragmentShaderCode + "color.b = yuv0 + 1.771 * yuv1 - 0.8855;\n";
        fragmentShaderCode = fragmentShaderCode + "color.b = (color.b < 0.0) ? 0.0 : ((color.b > 1.0) ? 1.0 : color.b);\n";
        fragmentShaderCode = fragmentShaderCode + "gl_FragColor = color;\n";
        fragmentShaderCode = fragmentShaderCode + "}\n";

        String vertexShaderCode = "uniform mat4 uMVPMatrix;   \n";
        vertexShaderCode = vertexShaderCode + "attribute vec4 vPosition;  \n";
        vertexShaderCode = vertexShaderCode + "attribute vec4 myTexCoord; \n";
        vertexShaderCode = vertexShaderCode + "varying vec4 VaryingTexCoord0; \n";
        vertexShaderCode = vertexShaderCode + "void main(){               \n";
        vertexShaderCode = vertexShaderCode + "VaryingTexCoord0 = myTexCoord; \n";
        vertexShaderCode = vertexShaderCode + "gl_Position = vPosition; \n";
        vertexShaderCode = vertexShaderCode + "}  \n";

        int[] arrayOfInt = new int[1];
        int i = compileShader(vertexShaderCode, GLES20.GL_VERTEX_SHADER);
        this.vertexShader = i;
        if (i == 0) {
            Log.e("createShaders", "failed when compileShader(vertex)");
        }
        int j = compileShader(fragmentShaderCode, GLES20.GL_FRAGMENT_SHADER);
        this.yuvFragmentShader = j;
        if (j == 0) {
            Log.e("createShaders", "failed when compileShader(fragment)");
        }
        this.programHandle = GLES20.glCreateProgram();
        GLES20.glAttachShader(this.programHandle, this.vertexShader);
        GLES20.glAttachShader(this.programHandle, this.yuvFragmentShader);
        GLES20.glLinkProgram(this.programHandle);
        GLES20.glGetProgramiv(this.programHandle, GLES20.GL_LINK_STATUS, arrayOfInt, 0);
        if (arrayOfInt[0] == 0) {
            Log.e("createShaders",
                    "link program err:" +
                            GLES20.glGetProgramInfoLog(this.programHandle));
            destroyShaders();
        }
        this.texRangeSlot = GLES20.glGetAttribLocation(this.programHandle,
                "myTexCoord");

        this.textureSlot[0] = GLES20.glGetUniformLocation(this.programHandle,
                "Ytex");
        this.textureSlot[1] = GLES20.glGetUniformLocation(this.programHandle,
                "Utex");
        this.textureSlot[2] = GLES20.glGetUniformLocation(this.programHandle,
                "Vtex");

        this.positionSlot = GLES20.glGetAttribLocation(this.programHandle,
                "vPosition");


        return 0L;
    }

    public long destroyShaders() {
        if (this.programHandle != 0) {
            GLES20.glDetachShader(this.programHandle, this.yuvFragmentShader);
            GLES20.glDetachShader(this.programHandle, this.vertexShader);
            GLES20.glDeleteProgram(this.programHandle);
            this.programHandle = 0;
        }
        if (this.yuvFragmentShader != 0) {
            GLES20.glDeleteShader(this.yuvFragmentShader);
            this.yuvFragmentShader = 0;
        }
        if (this.vertexShader != 0) {
            GLES20.glDeleteShader(this.vertexShader);
            this.vertexShader = 0;
        }
        return 0L;
    }

    public int draw(ByteBuffer yByteBuffer, ByteBuffer uByteBuffer, ByteBuffer vByteBuffer, int width, int height) {
//        GLES20.glClearColor(0.0F, 0.0F, 0.0F, 1.0F);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glUseProgram(this.programHandle);
        yByteBuffer.position(0);
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        loadTexture(this.texture[0], width, height, yByteBuffer);
        uByteBuffer.position(0);
        GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
        loadTexture(this.texture[1], width >> 1, height >> 1,
                uByteBuffer);
        vByteBuffer.position(0);
        GLES20.glActiveTexture(GLES20.GL_TEXTURE2);
        loadTexture(this.texture[2], width >> 1, height >> 1,
                vByteBuffer);
        GLES20.glUniform1i(this.textureSlot[0], 0);
        GLES20.glUniform1i(this.textureSlot[1], 1);
        GLES20.glUniform1i(this.textureSlot[2], 2);

        this.positionBuffer.position(0);
        GLES20.glEnableVertexAttribArray(this.positionSlot);
        GLES20.glVertexAttribPointer(this.positionSlot, 4, GLES20.GL_FLOAT,
                false, 0, this.positionBuffer);


        this.textCoodBuffer.position(0);

        GLES20.glEnableVertexAttribArray(this.texRangeSlot);
        GLES20.glVertexAttribPointer(this.texRangeSlot, 4, GLES20.GL_FLOAT,
                false, 0, this.textCoodBuffer);

        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        GLES20.glDisableVertexAttribArray(this.positionSlot);

        GLES20.glDisableVertexAttribArray(this.texRangeSlot);


        return 0;
    }

    public int loadTexture(int paramInt1, int paramInt2, int paramInt3, Buffer paramBuffer) {
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, paramInt1);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, paramInt2, paramInt3, 0, GLES20.GL_LUMINANCE,
                GLES20.GL_UNSIGNED_BYTE, paramBuffer);
        return 0;
    }

    public int loadVBOs() {
        this.textCoodBuffer =
                ByteBuffer.allocateDirect(4 * this.textCoodBufferData.length)
                        .order(ByteOrder.nativeOrder()).asFloatBuffer();
        this.textCoodBuffer.put(this.textCoodBufferData).position(0);

        this.positionBuffer =
                ByteBuffer.allocateDirect(4 * this.positionBufferData.length)
                        .order(ByteOrder.nativeOrder()).asFloatBuffer();
        this.positionBuffer.put(this.positionBufferData).position(0);

        return 0;
    }

    public void onDrawFrame(GL10 gl) {
//        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        synchronized (this) {
            if ((this.mWidth == 0) || (this.mHeight == 0) ||
                    (this.mYByteBuffer == null) ||
                    (this.mUByteBuffer == null) ||
                    (this.mVByteBuffer == null)) {
                return;
            }
            if (this.bNeedSleep) {
                try {
                    Thread.sleep(10L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.bNeedSleep = true;

//            gl.glViewport(this._x, this._y, this.width, this.height);
            changeGLViewport();
            draw(this.mYByteBuffer, this.mUByteBuffer, this.mVByteBuffer,
                    this.mWidth, this.mHeight);
        }
    }

    public void onSurfaceChanged(GL10 gl, int width, int height) {
        this.width = width;
        this.height = height;
        int x = 0;
        int y = 0;
        int videoWidth = 16 * height / 9;
        int videoHeight = height;
        if(videoWidth < width){//外层容器太宽了
            if(1 == width - videoWidth){
                videoWidth = width;
            }else{
                x = (width - videoWidth)/2;
            }
        }else{//外层容器太窄了
            videoHeight = 9 * width / 16;
            if(1 == height - videoHeight){
                videoHeight = height;
            }else{
                y = (height - videoHeight)/2;
            }
            videoWidth = width;
        }
        this.x = x;
        this.y = y;
        this.videoWidth = videoWidth;
        this.videoHeight = videoHeight;
        changeGLViewport();
    }

    private void changeGLViewport() {
        //高宽比计算偏移
        //先固定保持16:9

        int videoWidth = (int)(this.videoWidth*scale);
        int videoHeight = (int)(this.videoHeight*scale);

        GLES20.glViewport(x + dx, y + dy, videoWidth, videoHeight);
//        GLES20.glViewport(x + dx, y + dy, currentWidth, currentHeight);
    }

    public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
//        GLES20.glClearColor(0.0F, 0.0F, 0.0F, 1.0F);
        GLES20.glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
        GLES20.glGenTextures(GLES20.GL_LINE_STRIP, this.texture, 0);
        createShaders();
        loadVBOs();
    }

    public int unloadVBOs() {
        if (this.positionBuffer != null) {
            this.positionBuffer = null;
        }
        return 0;
    }

    int writeSample(byte[] paramArrayOfByte, int width, int height) {
        synchronized (this) {
            if ((width == 0) || (height == 0)) {
                return 0;
            }
            if ((width != this.mWidth) || (height != this.mHeight)) {
                this.mWidth = width;
                this.mHeight = height;
                this.mYByteBuffer = ByteBuffer.allocate(this.mWidth *
                        this.mHeight);
                this.mUByteBuffer = ByteBuffer.allocate(this.mWidth *
                        this.mHeight / 4);
                this.mVByteBuffer = ByteBuffer.allocate(this.mWidth *
                        this.mHeight / 4);
            }
            if (this.mYByteBuffer != null) {
                this.mYByteBuffer.position(0);
                this.mYByteBuffer.put(paramArrayOfByte, 0, this.mWidth *
                        this.mHeight);
                this.mYByteBuffer.position(0);
            }
            if (this.mVByteBuffer != null) {
                this.mVByteBuffer.position(0);


                this.mVByteBuffer.put(paramArrayOfByte,
                        5 * (this.mWidth * this.mHeight) / 4, this.mWidth *
                                this.mHeight / 4);
                this.mVByteBuffer.position(0);
            }
            if (this.mUByteBuffer != null) {
                this.mUByteBuffer.position(0);

                this.mUByteBuffer.put(paramArrayOfByte, this.mWidth *
                        this.mHeight, this.mWidth * this.mHeight / 4);


                this.mUByteBuffer.position(0);
            }
            this.bNeedSleep = false;

            return 1;
        }
    }

    public void cleanWithRGB(float r, float g, float b) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glClearColor(r, g, b, 1.0F);
        GLES20.glUseProgram(this.programHandle);
    }

    private int dx;
    private int dy;
    private float scale = 1;

    void setTrans(float dx,float dy){
        this.dx += dx;
        this.dy += dy;
        computeTrans();
    }

    void setScale(float focusX, float focusY, float scaleX, float scaleY, float scale){
        Log.d(TAG,String.format("onscale: focusX: %f, focusY: %f", focusX, focusY));
        /*if(scale > this.scale){
            focusX = 540;
            focusY = 450;
            scale = 2;
        }*/
        float fx = focusX - dx; // to gl x
        float fy = height - focusY - dy; // to gl y
        float dWidth = videoWidth*(scale - 1);
        float dHeight = videoHeight*(scale - 1);
//        Log.d(TAG,String.format("onscale set scale: %f, fx: %f, fy: %f",scale, fx, fy));
        computeScaleTrans(focusX, focusY, scale);
        this.scale = scale;
        computeTrans();
    }

    private void computeScaleTrans(float focusX, float focusY, float scale){
        float fx = focusX - dx - x ; // to gl x
        float fy = height - focusY - dy - y; // to gl y
        float dWidth = videoWidth*(scale - this.scale);
        float dHeight = videoHeight*(scale - this.scale);
        this.dx -= dWidth * fx / (videoWidth*this.scale);
        this.dy -= dHeight * fy / (videoHeight*this.scale);
        Log.d(TAG,String.format("onscale set scale: %f, fx: %f, fy: %f, dx: %d, dy: %d",scale, fx, fy, this.dx, this.dy));
    }

    private void computeTrans(){
        this.dx = Math.max(Math.min(this.dx,0),(int)(videoWidth*(1 - scale)));
        this.dy = Math.max(Math.min(this.dy,0),(int)(videoHeight*(1 - scale)));
    }
}